package com.shenhaofeng.user;

import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.CallSuper;
import android.util.Log;

import java.security.InvalidParameterException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;


/**
 * 用户管理基类，用于账户的管理，用户的各种属性的存储记录登录状态和账户,继承该类并创建一个实例的初始化方法
 * Created by Administrator on 2016/9/9.
 */
public abstract class BaseUserManager<U> {


    private static final String TAG = "BaseUserManager";

    private static final String SP_NAME = "user";


    private static final String KEY_USERNAME = "base_username";

    private static final String KEY_TOKEN = "base_token";

    private static final String KEY_STATUS = "base_status";

    private Context mContext;


    private SharedPreferences mUserPreferences;

    //登录状态
    private LoginStatus mLoginStatus;

    /**
     * 属性映射器，将从服务器获取的用户数据映射到Bundle中保存到本地及内存中
     */
    private PropertyBridge<U> mPropertyBridge;

    /**
     * 内存中的用户属性集合
     */
    private PropertyBundle mUserProperty;


    /**
     * 默认的构造方法
     *
     * @param context 上下文对象
     */
    protected BaseUserManager(Context context) {
        //初始化必须的各个对象
        this.mContext = context.getApplicationContext();
        mLoginStatus = new LoginStatus();
        mUserProperty = new PropertyBundle();
        mPropertyBridge = onCreatePropertyBridge();
        mUserPreferences = mContext.getSharedPreferences(getPreferencesName(), Context.MODE_PRIVATE);
        //调用管理器的生命周期方法,用户可以处理一些初始化方法
        onCreate(mLoginStatus, mUserProperty);
    }


    /**
     * 检查用户是否已登录
     *
     * @return true标识已登录，反之未登录
     */
    public boolean hasLoggedIn() {
        return mLoginStatus.status == LoginStatus.STATUS_LOGGED_IN;
    }


    /**
     * 获取用户名
     *
     * @return 用户名，用户的唯一标识
     */
    public String getUsername() {
        return mLoginStatus.mUsername;
    }


    /**
     * 标记为登录状态,用户成功登录后调用方法,需要注意的是如果原本已有账户登录会先注销原有的账户
     *
     * @param user 用户自定义的user对象
     */
    public void login(U user) {
        if(hasLoggedIn()){
            //如果是登录状态将会注销原来的用户
            logout();
        }
        //将属性映射到内存中
        mPropertyBridge.onMapProperty(user, mUserProperty);
        //标记内存中登录的用户名
        mLoginStatus.mUsername = mPropertyBridge.onMapUsername(user);
        //标记内存中的token
        mLoginStatus.token = mPropertyBridge.onMapToken(user);
        //标记内存中你的登录状态
        mLoginStatus.status = LoginStatus.STATUS_LOGGED_IN;
        //将属性保存到本地文件
        onSaveLocalUser(mLoginStatus, mUserProperty);
        onLoggedIn(mLoginStatus, mUserProperty);
    }


    /**
     * 注销登陆的账户,如果未登录则没有任何影响
     */
    public void logout() {
        String username = mLoginStatus.mUsername;
        PropertyBundle propertyBundle = new PropertyBundle(mUserProperty);
        //清空本地的账户数据
        mUserPreferences.edit().clear().apply();
        //清除内存数据
        mUserProperty.clear();
        //标记未登录状态
        mLoginStatus.status = LoginStatus.STATUS_LOGGED_OFF;
        //清空登录的账户名
        mLoginStatus.mUsername = null;
        //清空授权令牌
        mLoginStatus.token = null;
        onLoggedOut(username, propertyBundle);
    }


    /**
     * 更新整个用户数据
     *
     * @param user 用户自定义的user对象
     */
    public void updateProperty(U user) {
        //首先需要清空内存中的数据
        mUserProperty.clear();
        //读取新的属性到内存
        mPropertyBridge.onMapProperty(user, mUserProperty);
        //获取改变的所有键
        Iterator<String> keyIterator = mUserProperty.keySet().iterator();
        String[] keys = new String[mUserProperty.keySet().size()];
        int i = 0;
        while (keyIterator.hasNext()) {
            String key = keyIterator.next();
            keys[i] = key;
            i++;
        }
        //同样需要清空文件中的数据
        mUserPreferences.edit().clear().apply();
        //保存本地用户数据
        onSaveLocalUser(mLoginStatus, mUserProperty);
        //通知属性变更
        onUpdateUserProperty(keys);
    }


    /**
     * 获取授权令牌
     *
     * @return 授权令牌
     */
    public String getToken() {
        return mLoginStatus.token;
    }


    /**
     * 调用该方法实现快速登录,一般情况向，这里应该是发送一个Intent来启动一个登录界面
     */
    public abstract void toLogin();


    /**
     * 为实现类提供一个属性写入的方法入口
     *
     * @param key   属性名
     * @param value 属性值，字符串形式
     * @return 如果本地属性写入失败，会返回false，同时内存中也不会保存相关属性
     */
    protected boolean putProperty(String key, String value) {
        //保存新的属性到内存和本地
        return putSafeProperty(key, value);
    }


    /**
     * 为实现类提供一个属性写入的方法入口
     *
     * @param key   属性名
     * @param value 属性值，int形式
     * @return 如果本地属性写入失败，会返回false，同时内存中也不会保存相关属性
     */
    protected boolean putProperty(String key, int value) {
        //保存新的属性到内存和本地
        return putSafeProperty(key, value);
    }


    /**
     * 为实现类提供一个属性写入的方法入口
     *
     * @param key   属性名
     * @param value 属性值，float形式
     * @return 如果本地属性写入失败，会返回false，同时内存中也不会保存相关属性
     */
    protected boolean putProperty(String key, float value) {
        //保存新的属性到内存和本地
        return putSafeProperty(key, value);
    }


    /**
     * 为实现类提供一个属性写入的方法入口
     *
     * @param key   属性名
     * @param value 属性值，long形式
     * @return 如果本地属性写入失败，会返回false，同时内存中也不会保存相关属性
     */
    protected boolean putProperty(String key, long value) {
        //保存新的属性到内存和本地
        return putSafeProperty(key, value);
    }


    /**
     * 为实现类提供一个属性写入的方法入口
     *
     * @param key   属性名
     * @param value 属性值，boolean形式
     * @return 如果本地属性写入失败，会返回false，同时内存中也不会保存相关属性
     */
    protected boolean putProperty(String key, boolean value) {
        //保存新的属性到内存和本地
        return putSafeProperty(key, value);
    }


    /**
     * 为实现类提供一个属性写入的方法入口
     *
     * @param key   属性名
     * @param value 属性值，Set形式
     * @return 如果本地属性写入失败，会返回false，同时内存中也不会保存相关属性
     */
    protected boolean putProperty(String key, Set<String> value) {
        //保存新的属性到内存和本地
        return putSafeProperty(key, value);
    }


    /**
     * 更新用户属性
     *
     * @param key   需要更新的字段，如果没有则插入
     * @param value 需要更新的字段值
     */
    protected void updateProperty(String key, String value) {
        //保存新的属性到内存和本地
        putProperty(key, value);
        //通知属性变更
        onUpdateUserProperty(key);
    }


    /**
     * 更新用户属性
     *
     * @param key   需要更新的字段，如果没有则插入
     * @param value 需要更新的字段值
     */
    protected void updateProperty(String key, int value) {
        //保存新的属性到内存和本地
        putProperty(key, value);
        //通知属性变更
        onUpdateUserProperty(key);
    }


    /**
     * 更新用户属性
     *
     * @param key   需要更新的字段，如果没有则插入
     * @param value 需要更新的字段值
     */
    protected void updateProperty(String key, long value) {
        //保存新的属性到内存和本地
        putProperty(key, value);
        //通知属性变更
        onUpdateUserProperty(key);
    }


    /**
     * 更新用户属性
     *
     * @param key   需要更新的字段，如果没有则插入
     * @param value 需要更新的字段值
     */
    protected void updateProperty(String key, float value) {
        //保存新的属性到内存和本地
        putProperty(key, value);
        //通知属性变更
        onUpdateUserProperty(key);
    }


    /**
     * 更新用户属性
     *
     * @param key   需要更新的字段，如果没有则插入
     * @param value 需要更新的字段值
     */
    protected void updateProperty(String key, boolean value) {
        //保存新的属性到内存和本地
        putProperty(key, value);
        //通知属性变更
        onUpdateUserProperty(key);
    }


    /**
     * 更新用户属性
     *
     * @param key   需要更新的字段，如果没有则插入
     * @param value 需要更新的字段值
     */
    protected void updateProperty(String key, Set<String> value) {
        //保存新的属性到内存和本地
        putProperty(key, value);
        //通知属性变更
        onUpdateUserProperty(key);
    }


    /**
     * 读取用户整形属性
     *
     * @param key
     * @param defaultValue
     * @return
     */
    protected int getIntProperty(String key, int defaultValue) {
        if (!hasLoggedIn()) {
            Log.e(TAG, "当前为未登录,无法读取用户属性");
            return defaultValue;
        }
        return mUserProperty.getInt(key, defaultValue);
    }


    /**
     * 读取用户浮点型属性
     *
     * @param key
     * @param defaultValue
     * @return
     */
    protected float getFloatProperty(String key, float defaultValue) {
        if (!hasLoggedIn()) {
            Log.e(TAG, "当前为未登录,无法读取用户属性");
            return defaultValue;
        }
        return mUserProperty.getFloat(key, defaultValue);
    }


    /**
     * 读取用户长整型属性
     *
     * @param key
     * @param defaultValue
     * @return
     */
    protected float getLongProperty(String key, long defaultValue) {
        if (!hasLoggedIn()) {
            Log.e(TAG, "当前为未登录,无法读取用户属性");
            return defaultValue;
        }
        return mUserProperty.getLong(key, defaultValue);
    }


    /**
     * 读取用户布尔型属性
     *
     * @param key
     * @param defaultValue
     * @return
     */
    protected boolean getBooleanProperty(String key, boolean defaultValue) {
        if (!hasLoggedIn()) {
            Log.e(TAG, "当前为未登录,无法读取用户属性");
            return defaultValue;
        }
        return mUserProperty.getBoolean(key, defaultValue);
    }


    /**
     * 读取用户字符串属性
     *
     * @param key
     * @param defaultValue
     * @return
     */
    protected String getStringProperty(String key, String defaultValue) {
        if (!hasLoggedIn()) {
            Log.e(TAG, "当前为未登录,无法读取用户属性");
            return defaultValue;
        }
        return mUserProperty.getString(key, defaultValue);
    }


    /**
     * 读取用户字符串属性
     *
     * @param key
     * @return
     */
    protected String getStringProperty(String key) {
        if (!hasLoggedIn()) {
            Log.e(TAG, "当前为未登录,无法读取用户属性");
            return null;
        }
        return mUserProperty.getString(key);
    }


    /**
     * 读取用户字符串集合属性
     *
     * @param key
     * @param defaultValue
     * @return
     */
    protected Set<String> getStringSetProperty(String key, Set<String> defaultValue) {
        if (!hasLoggedIn()) {
            Log.e(TAG, "当前为未登录,无法读取用户属性");
            return defaultValue;
        }
        return mUserProperty.getStringSet(key, defaultValue);
    }


    /**
     * 删除某个属性
     *
     * @param key 需要删除的字段
     */
    protected void removeProperty(String key) {
        if (!hasLoggedIn()) {
            Log.e(TAG, "当前为未登录,无法删除用户属性");
            return;
        }
        //删除内存中的属性字段
        mUserProperty.remove(key);
        //删除文件中的属性字段
        mUserPreferences.edit().remove(key).apply();
        ;
    }


    /**
     * 在创建或打开属性文件时提供的文件名,覆写该方法可以自定义文件名
     *
     * @return user文件名
     */
    protected String getPreferencesName() {
        return SP_NAME;
    }


    /**
     * 获取上下文对象
     *
     * @return 上下文对象
     */
    protected Context getContext() {
        return mContext;
    }


    /**
     * 提供一个属性桥接器
     *
     * @return 一个桥接器对象
     */
    protected abstract PropertyBridge<U> onCreatePropertyBridge();


    /**
     * 在该方法中初始化
     *
     * @param mLoginStatus  登录状态
     * @param mUserProperty 用户的属性字段集合
     */
    @CallSuper
    protected void onCreate(LoginStatus mLoginStatus, PropertyBundle mUserProperty) {
        /*
        检查登录状态，分别做初始化处理
         */
        if (mUserProperty.getBoolean(KEY_STATUS, false)) {
            //检查是否登录，如果登录需要立即检查用户名和用户属性，并同步到内存中
            mLoginStatus.status = LoginStatus.STATUS_LOGGED_IN;
            mLoginStatus.mUsername = mUserPreferences.getString(KEY_USERNAME, null);
            mLoginStatus.token = mUserPreferences.getString(KEY_TOKEN, null);
            //找到所有保存的属性
            Map<String, ?> propertyMap = mUserPreferences.getAll();
            Iterator<String> keys = propertyMap.keySet().iterator();
            //将属性遍历到内存中
            while (keys.hasNext()) {
                String key = keys.next();
                Object value = propertyMap.get(key);
                readProperty(mUserProperty, key, value);
            }
        } else {
            //未登录重置登录状态
            mLoginStatus.status = LoginStatus.STATUS_LOGGED_OFF;
            mLoginStatus.mUsername = null;
        }
    }


    /**
     * 保存本地的用户数据
     *
     * @param loginStatus 登录状态
     * @param property    用户的属性字段集合
     */
    @CallSuper
    protected void onSaveLocalUser(LoginStatus loginStatus, PropertyBundle property) {
        //该方法实在用户需要保存用户数据是被调用,只有在登录状态下才会保存数据反之会清空用户数据
        if (loginStatus.status == LoginStatus.STATUS_LOGGED_IN) {
            String username = loginStatus.mUsername;
            String token = loginStatus.token;
            SharedPreferences.Editor editor = mUserPreferences.edit()
                    .putBoolean(KEY_STATUS, true)
                    .putString(KEY_USERNAME, username)
                    .putString(KEY_TOKEN, token);
            Iterator<String> iterator = property.keySet().iterator();
            while (iterator.hasNext()) {
                String key = iterator.next();
                Object value = property.get(key);
                if (value != null) {
                    if (String.class.isInstance(value)) {
                        editor.putString(key, String.class.cast(value));
                    } else if (Integer.class.isInstance(value)) {
                        editor.putInt(key, Integer.class.cast(value));
                    } else if (Long.class.isInstance(value)) {
                        editor.putLong(key, Long.class.cast(value));
                    } else if (Float.class.isInstance(value)) {
                        editor.putFloat(key, Float.class.cast(value));
                    } else if (Boolean.class.isInstance(value)) {
                        editor.putBoolean(key, Boolean.class.cast(value));
                    } else if (Set.class.isInstance(value)) {
                        editor.putStringSet(key, Set.class.cast(value));
                    }


                }
            }
            editor.apply();
        } else {
            //未登录状态,清除数据
            mUserPreferences.edit().clear().apply();
        }

    }


    /**
     * 用户登录后被调用
     *
     * @param loginStatus    登录状态
     * @param propertyBundle 用户的属性字段集合
     */
    protected void onLoggedIn(LoginStatus loginStatus, PropertyBundle propertyBundle) {

    }


    /**
     * 用户清空前被调用
     *
     * @param username      用户名
     * @param mUserProperty 用户的属性字段集合
     */
    protected void onLoggedOut(String username, PropertyBundle mUserProperty) {

    }


    /**
     * 在用户属性字段发生改变时将会被调用
     *
     * @param keys 发生了改变的字段key
     */
    protected void onUpdateUserProperty(String... keys) {

    }


    /*
    ================================================内部方法=============================================================
     */


    /**
     * 属性写入的一个入口，通过该方法可以实现属性的储存,将user数据保存到内存中
     *
     * @param mUserProperty 需要存储的属性集
     * @param key           属性名
     * @param value         属性值
     */
    private void readProperty(PropertyBundle mUserProperty, String key, Object value) {
        Class<?> valueCls = value.getClass();
        if (valueCls.equals(Integer.class)) {
            mUserProperty.putInt(key, Integer.class.cast(value));
        } else if (valueCls.equals(Long.class)) {
            mUserProperty.putLong(key, Long.class.cast(value));
        } else if (valueCls.equals(Float.class)) {
            mUserProperty.putFloat(key, Float.class.cast(value));
        } else if (valueCls.equals(Boolean.class)) {
            mUserProperty.putBoolean(key, Boolean.class.cast(value));
        } else if (valueCls.equals(String.class)) {
            mUserProperty.putString(key, String.class.cast(value));
        } else if (valueCls.equals(Set.class)) {
            mUserProperty.putStringSet(key, Set.class.cast(value));
        } else {
            throw new InvalidParameterException("无效的User属性类型");
        }
    }


    /**
     * 提供一个安全的将属性保存到本地和内存的方法
     *
     * @param key
     * @param value
     * @return
     */
    private boolean putSafeProperty(String key, Object value) {
        if (!hasLoggedIn()) {
            Log.e(TAG, "当前为未登录,无法保存用户属性");
            return false;
        }
        SharedPreferences.Editor editor = mUserPreferences.edit();
        if (value == null) {
            //value是null的话,删除这个属性
            editor.remove(key).apply();
            mUserProperty.remove(key);
            return true;
        }
        //属性都是有效的
        Class<?> valueClass = value.getClass();
        if (valueClass.equals(String.class)) {
            //String
            editor.putString(key, String.class.cast(value));
        } else if (valueClass.equals(Integer.class)) {
            //Integer
            editor.putInt(key, Integer.class.cast(value));
        } else if (valueClass.equals(Float.class)) {
            //Float
            editor.putFloat(key, Float.class.cast(value));
        } else if (valueClass.equals(Long.class)) {
            //Long
            editor.putLong(key, Long.class.cast(value));
        } else if (valueClass.equals(Boolean.class)) {
            //Boolean
            editor.putBoolean(key, Boolean.class.cast(value));
        } else if (valueClass.equals(Set.class)) {
            //Set<String>
            editor.putStringSet(key, Set.class.cast(value));
        } else {
            throw new IllegalArgumentException("不支持的属性类型");
        }
        boolean result = editor.commit();
        if (!result) {
            //本地提交失败
            return false;
        }
        //保存属性到内存中
        if (valueClass.equals(String.class)) {
            //String
            mUserProperty.putString(key, String.class.cast(value));
        } else if (valueClass.equals(Integer.class)) {
            //Integer
            mUserProperty.putInt(key, Integer.class.cast(value));
        } else if (valueClass.equals(Float.class)) {
            //Float
            mUserProperty.putFloat(key, Float.class.cast(value));
        } else if (valueClass.equals(Long.class)) {
            //Long
            mUserProperty.putLong(key, Long.class.cast(value));
        } else if (valueClass.equals(Boolean.class)) {
            //Boolean
            mUserProperty.putBoolean(key, Boolean.class.cast(value));
        } else if (valueClass.equals(Set.class)) {
            //Set<String>
            Set<String> set = Set.class.cast(value);
            mUserProperty.putStringSet(key, set);
        } else {
            throw new IllegalArgumentException("不支持的属性类型");
        }

        return true;
    }


    /**
     * 登录状态
     */
    public static class LoginStatus {

        /**
         * 该状态表示用户正处于未登录状态
         */
        public static final int STATUS_LOGGED_OFF = 0;

        /**
         * 该状态表示用户正处于登录状态
         */
        public static final int STATUS_LOGGED_IN = 1;


        /**
         * 登录的用户名,只有状态为STATUS_LOGGED_IN在不为空
         */
        protected String mUsername;


        /**
         * 当前登录状态
         */
        protected int status;

        /**
         * token，与服务器交互的证明
         */
        protected String token;


        /**
         * 获取用户名
         *
         * @return
         */
        public String getUsername() {
            return mUsername;
        }


        /**
         * 获取登录状态
         *
         * @return
         */
        public int getStatus() {
            return status;
        }


        /**
         * 获取token值
         *
         * @return
         */
        public String getToken() {
            return token;
        }
    }
}
